/*
 * OpenParts
 * A dynamic-loading components framework for GWT
 * Copyright (C) 2011 Christophe Bouthier  [chris{AT}binary-gastronome{DOT}fr]
 *
 * This work is partially based on work I have done at INRIA (http://www.inria.fr) 
 * in the context of the Qualipso European Project (http://qualipso.org/),
 * The work done at INRIA is Copyright (C) 2006-2011 INRIA
 *
 * This work is distributed under the LGPL version 3
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3
 * as published by the Free Software Foundation. See the GNU
 * Lesser General Public License in LGPL.txt for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package fr.openparts.OpenParts.test.gwt.tests;

import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.RootPanel;

import fr.openparts.OpenParts.client.OPMessagesDispatcher;
import fr.openparts.OpenParts.client.OPParams;
import fr.openparts.OpenParts.client.OPParamsJS;
import fr.openparts.OpenParts.client.OPSlotProxy;
import fr.openparts.OpenParts.client.OPViewProxy;

/**
 * GWT tests for OPMessagesDispatcher.
 * 
 * @author Christophe Bouthier [chris{AT}binary-gastronome{DOT}fr]
 * @creation.date 14 July 2010
 */
public class OPMessagesDispatcherTestGwt extends GWTTestCase {

    private static final String ID = "dummyId";
    private static final String MESSAGE = "dummyMessage";

    private OPMessagesDispatcher dispatcher;
    private OPParamsJS paramsjs;

    private int embeddedViewHandlerCounter = 0;
    private int containerSlotHandlerCounter = 0;
    private int embeddedViewAttachCounter = 0;

    /**
     * Return the name of the tested GWT Module.
     * 
     * @see com.google.gwt.junit.client.GWTTestCase#getModuleName()
     */
    @Override
    public final String getModuleName() {
        return "fr.openparts.OpenParts.test.gwt.TestOpenParts";
    }

    /**
     * Called at the start of each test to set up the service.
     * 
     * @see com.google.gwt.junit.client.GWTTestCase#gwtSetUp()
     */
    @Override
    protected void gwtSetUp() throws Exception {
        super.gwtSetUp();
        dispatcher = OPMessagesDispatcher.dispatcherFactory(ID);
        paramsjs = OPParamsJS.buildFromOPParams(new OPParams("dummyName", "dummyValue"));
    }

    /**
     * Called at the end of each test to clean up the mess.
     * 
     * @see com.google.gwt.junit.client.GWTTestCase#gwtTearDown()
     */
    @Override
    protected final void gwtTearDown() throws Exception {
        super.gwtTearDown();
        embeddedViewHandlerCounter = 0;
        containerSlotHandlerCounter = 0;
    }

    /**
     * Test creating an OPMessagesDispatcher. Should get something not null.
     */
    public final void testCreatingOPMessagesDispatcher() {
        assertNotNull("Messages dispatcher shouldn't be null", dispatcher);
    }

    /**
     * Test creating an OPMessagesDispatcher with a null id. Should raise an assertion error.
     */
    public final void testCreatingOPMessagesDispatcherWithNullId() {
        if (assertionsAreEnabled()) {
            try {
                OPMessagesDispatcher.dispatcherFactory(null);
                fail("OPMessagesDispatcher should raise an assertion error when creating a dispatcher with null id.");
            } catch (AssertionError e) {
                assertTrue("Error should be an AssertionError e", e instanceof AssertionError);
            }
        }
    }

    /**
     * Test creating an OPMessagesDispatcher with an empty id. Should raise an assertion error.
     */
    public final void testCreatingOPMessagesDispatcherWithEmptyId() {
        if (assertionsAreEnabled()) {
            try {
                OPMessagesDispatcher.dispatcherFactory("");
                fail("OPMessagesDispatcher should raise an assertion error when creating a dispatcher with empty id.");
            } catch (AssertionError e) {
                assertTrue("Error should be an AssertionError e", e instanceof AssertionError);
            }
        }
    }

    /**
     * Test getting the dispatcher id. Should get ID.
     */
    public final void testGettingId() {
        final String id = dispatcher.getId();
        assertNotNull("Id shouldn't be null", id);
        assertEquals("Id should be " + ID, ID, id);
    }

    /**
     * Test embedded view callbacks registration.
     */
    public final void testEmbeddedViewHandlerCallbackRegistration() {
        assertFalse("There should not be any embedded view callback registered yet", dispatcher.isMessageFromEmbeddedViewHandlerCallbackRegistered());
        assertFalse("There should not be any container slot callback registered yet", dispatcher.isMessageFromContainerSlotHandlerCallbackRegistered());
        registerDummyEmbeddedViewHandlerCallback(dispatcher, this);
        assertTrue("There should be an embedded view callback registered now", dispatcher.isMessageFromEmbeddedViewHandlerCallbackRegistered());
        assertFalse("There should still not be any container slot callback registered", dispatcher.isMessageFromContainerSlotHandlerCallbackRegistered());
    }

    /**
     * Test container slot callbacks registration.
     */
    public final void testContainerSlotHandlerCallbackRegistration() {
        assertFalse("There should not be any embedded view callback registered yet", dispatcher.isMessageFromEmbeddedViewHandlerCallbackRegistered());
        assertFalse("There should not be any container slot callback registered yet", dispatcher.isMessageFromContainerSlotHandlerCallbackRegistered());
        registerDummyContainerSlotHandlerCallback(dispatcher, this);
        assertFalse("There should still not be any embedded view callback registered", dispatcher.isMessageFromEmbeddedViewHandlerCallbackRegistered());
        assertTrue("There should be a container slot callback registered now", dispatcher.isMessageFromContainerSlotHandlerCallbackRegistered());
    }

    /**
     * Test dispatching message to embedded view.
     */
    public final void testDispatchingMessageToEmbeddedView() {
        registerDummyEmbeddedViewHandlerCallback(dispatcher, this);
        registerDummyContainerSlotHandlerCallback(dispatcher, this);
        assertEquals("message from embedded view handler callback should not have been called yet", 0, embeddedViewHandlerCounter);
        assertEquals("message from container slot handler callback should not have been called yet", 0, containerSlotHandlerCounter);
        dispatcher.dispatchMessageToEmbeddedView(MESSAGE, paramsjs);
        assertEquals("message from embedded view handler callback should still not have been called", 0, embeddedViewHandlerCounter);
        assertEquals("message from container slot handler callback should have been called now", 1, containerSlotHandlerCounter);
    }

    /**
     * Test dispatching null message to embedded view. Should raise an assertion error.
     */
    public final void testDispatchingNullMessageToEmbeddedView() {
        if (assertionsAreEnabled()) {
            try {
                dispatcher.dispatchMessageToEmbeddedView(null, paramsjs);
                fail("OPMessagesDispatcher should raise an assertion error when dispatching null message to embedded view.");
            } catch (AssertionError e) {
                assertTrue("Error should be an AssertionError e", e instanceof AssertionError);
            }
        }
    }

    /**
     * Test dispatching message with null params to embedded view. Shouldn't get an error.
     */
    public final void testDispatchingMessageToEmbeddedViewWithNullParams() {
        registerDummyEmbeddedViewHandlerCallback(dispatcher, this);
        registerDummyContainerSlotHandlerCallback(dispatcher, this);
        assertEquals("message from embedded view handler callback should not have been called yet", 0, embeddedViewHandlerCounter);
        assertEquals("message from container slot handler callback should not have been called yet", 0, containerSlotHandlerCounter);
        dispatcher.dispatchMessageToEmbeddedView(MESSAGE, null);
        assertEquals("message from embedded view handler callback should still not have been called", 0, embeddedViewHandlerCounter);
        assertEquals("message from container slot handler callback should have been called now", 1, containerSlotHandlerCounter);
    }

    /**
     * Test dispatching message to container slot.
     */
    public final void testDispatchingMessageToContainerSlot() {
        registerDummyEmbeddedViewHandlerCallback(dispatcher, this);
        registerDummyContainerSlotHandlerCallback(dispatcher, this);
        assertEquals("message from embedded view handler callback should not have been called yet", 0, embeddedViewHandlerCounter);
        assertEquals("message from container slot handler callback should not have been called yet", 0, containerSlotHandlerCounter);
        dispatcher.dispatchMessageToContainerSlot(MESSAGE, paramsjs);
        assertEquals("message from embedded view handler callback should have been called now", 1, embeddedViewHandlerCounter);
        assertEquals("message from container slot handler callback should still not have been called", 0, containerSlotHandlerCounter);
    }

    /**
     * Test dispatching null message to container slot. Should raise an assertion error.
     */
    public final void testDispatchingNullMessageToContainerSlot() {
        if (assertionsAreEnabled()) {
            try {
                dispatcher.dispatchMessageToContainerSlot(null, paramsjs);
                fail("OPMessagesDispatcher should raise an assertion error when dispatching null message to container slot.");
            } catch (AssertionError e) {
                assertTrue("Error should be an AssertionError e", e instanceof AssertionError);
            }
        }
    }

    /**
     * Test dispatching message to container slot with null params. Shouldn't get an error.
     */
    public final void testDispatchingMessageToContainerSlotWithNullParams() {
        registerDummyEmbeddedViewHandlerCallback(dispatcher, this);
        registerDummyContainerSlotHandlerCallback(dispatcher, this);
        assertEquals("message from embedded view handler callback should not have been called yet", 0, embeddedViewHandlerCounter);
        assertEquals("message from container slot handler callback should not have been called yet", 0, containerSlotHandlerCounter);
        dispatcher.dispatchMessageToContainerSlot(MESSAGE, null);
        assertEquals("message from embedded view handler callback should have been called now", 1, embeddedViewHandlerCounter);
        assertEquals("message from container slot handler callback should still not have been called", 0, containerSlotHandlerCounter);
    }
    
    /**
     * Test informing about embedded view attachment.
     */
    public final void testOnEmbeddedViewAttach() {
        registerDummyOnEmbeddedViewAttachCallback(dispatcher, this);
        assertEquals("embedded view attachment callback should not have been called yet", 0, embeddedViewAttachCounter);
        dispatcher.onEmbeddedViewAttach();
        assertEquals("embedded view attachment callback should have been called now", 1, embeddedViewAttachCounter);
    }

    /**
     * Test attaching a dispatcher to a given element. Should get back the dispatcher.
     */
    public final void testAttachingDispatcherToElement() {
        final Element element = DOM.createDiv();

        assertNull("There should not be any dispatcher attached yet to the element", getDispatcherAttachedToElementl(element));

        dispatcher.attachDispatcherOnElement(element);

        assertNotNull("There should be a dispatcher attached to the element", getDispatcherAttachedToElementl(element));
        assertEquals("The attached dispatcher should be the same than the one put before", dispatcher, getDispatcherAttachedToElementl(element));
    }

    /**
     * Test attaching a dispatcher to a null element. Should raise an assertion error.
     */
    public final void testAttachingDispatcherToNullElement() {
        if (assertionsAreEnabled()) {
            try {
                dispatcher.attachDispatcherOnElement(null);
                fail("OPMessagesDispatcher should raise an assertion error when attaching dispatcher to null element.");
            } catch (AssertionError e) {
                assertTrue("Error should be an AssertionError e", e instanceof AssertionError);
            }
        }
    }

    /**
     * Test attaching two dispatchers to a given element. Should get back only the second the dispatcher.
     */
    public final void testAttachingTwoDispatchersToElement() {
        final Element element = DOM.createDiv();

        assertNull("There should not be any dispatcher attached yet to the element", getDispatcherAttachedToElementl(element));

        dispatcher.attachDispatcherOnElement(element);
        final OPMessagesDispatcher dispatcher2 = OPMessagesDispatcher.dispatcherFactory(ID);
        dispatcher2.attachDispatcherOnElement(element);

        assertNotNull("There should be a dispatcher attached to the element", getDispatcherAttachedToElementl(element));
        assertEquals("The attached dispatcher should be the second one", dispatcher2, getDispatcherAttachedToElementl(element));
    }

    /**
     * Test getting a dispatcher attached to an ID. Should get back the dispatcher.
     */
    public final void testGettingDispatcherAttachedToID() {
        final Element element = DOM.createDiv();
        element.setId(ID);
        RootPanel.getBodyElement().appendChild(element);
        dispatcher.attachDispatcherOnElement(element);

        final OPMessagesDispatcher dispatcher2 = OPMessagesDispatcher.getDispatcherAttachedToId(ID);

        assertNotNull("Found dispatcher shouldn't be null", dispatcher2);

    }

    /**
     * Test getting a dispatcher attached to a null ID. Should raise an assertion error.
     */
    public final void testGettingDispatcherAttachedToNullId() {
        if (assertionsAreEnabled()) {
            try {
                OPMessagesDispatcher.getDispatcherAttachedToId(null);
                fail("OPMessagesDispatcher should raise an assertion error when trying to get the dispatcher attached to a null id.");
            } catch (AssertionError e) {
                assertTrue("Error should be an AssertionError e", e instanceof AssertionError);
            }
        }
    }

    /**
     * Test getting a dispatcher attached to an empty ID. Should raise an assertion error.
     */
    public final void testGettingDispatcherAttachedToEmptyId() {
        if (assertionsAreEnabled()) {
            try {
                OPMessagesDispatcher.getDispatcherAttachedToId("");
                fail("OPMessagesDispatcher should raise an assertion error when trying to get the dispatcher attached to an empty id.");
            } catch (AssertionError e) {
                assertTrue("Error should be an AssertionError e", e instanceof AssertionError);
            }
        }
    }

    /**
     * Test getting a dispatcher attached to an invalid ID. Should return null.
     */
    public final void testGettingDispatcherAttachedToInvalidId() {
        dispatcher = OPMessagesDispatcher.getDispatcherAttachedToId(ID + "_dummy");

        assertNull("There should be no dispatcher for an invalid ID", dispatcher);
    }

    /**
     * Test getting a dispatcher for an ID where there is no dispatcher attached. Should return null.
     */
    public final void testGettingDispatcherForIdWithNoDispatcherAttached() {
        final Element element = DOM.createDiv();
        element.setId(ID + "1");
        RootPanel.getBodyElement().appendChild(element);

        dispatcher = OPMessagesDispatcher.getDispatcherAttachedToId(ID + "1");

        assertNull("There should be no dispatcher for an ID with no dispatcher attached", dispatcher);
    }

    /**
     * Test registering a OPViewProxy.
     */
    public final void testRegisteringViewProxy() {
        assertFalse("There should not be any embedded view callback registered yet", dispatcher.isMessageFromEmbeddedViewHandlerCallbackRegistered());
        assertFalse("There should not be any container slot callback registered yet", dispatcher.isMessageFromContainerSlotHandlerCallbackRegistered());
        dispatcher.registerViewProxy(new OPViewProxy(ID));
        assertTrue("There should be an embedded view callback registered now", dispatcher.isMessageFromEmbeddedViewHandlerCallbackRegistered());
        assertFalse("There should still not be any container slot callback registered", dispatcher.isMessageFromContainerSlotHandlerCallbackRegistered());
    }
    
    /**
     * Test registering a null OPViewProxy.. Should raise an assertion error.
     */
    public final void testRegisteringNullViewProxy() {
        if (assertionsAreEnabled()) {
            try {
                dispatcher.registerViewProxy(null);
                fail("OPMessagesDispatcher should raise an assertion error when trying to register a null view proxy.");
            } catch (AssertionError e) {
                assertTrue("Error should be an AssertionError e", e instanceof AssertionError);
            }
        }
    }
    
    /**
     * Test registering a OPSlotProxy.
     */
    public final void testRegisteringSlotProxy() {
        assertFalse("There should not be any embedded view callback registered yet", dispatcher.isMessageFromEmbeddedViewHandlerCallbackRegistered());
        assertFalse("There should not be any container slot callback registered yet", dispatcher.isMessageFromContainerSlotHandlerCallbackRegistered());
        dispatcher.registerSlotProxy(new OPSlotProxy(ID));
        assertFalse("There should still not be any embedded view callback registered", dispatcher.isMessageFromEmbeddedViewHandlerCallbackRegistered());
        assertTrue("There should be a container slot callback registered now", dispatcher.isMessageFromContainerSlotHandlerCallbackRegistered());
    }
    
    /**
     * Test registering a null OPSlotProxy.. Should raise an assertion error.
     */
    public final void testRegisteringNullSlotProxy() {
        if (assertionsAreEnabled()) {
            try {
                dispatcher.registerSlotProxy(null);
                fail("OPMessagesDispatcher should raise an assertion error when trying to register a null slot proxy.");
            } catch (AssertionError e) {
                assertTrue("Error should be an AssertionError e", e instanceof AssertionError);
            }
        }
    }
    
    /**
     * Register a dummy message from embedded view handler callback.
     * 
     * @param dispatcher
     *            the dispatcher on which to register the handler callback.
     * @param tester
     *            this test class.
     */
    private final native void registerDummyEmbeddedViewHandlerCallback(final OPMessagesDispatcher dispatcher, final OPMessagesDispatcherTestGwt tester) /*-{
        dispatcher.messageFromEmbeddedViewHandlerCallback = function(message, paramsjs) {
            return $entry(tester.@fr.openparts.OpenParts.test.gwt.tests.OPMessagesDispatcherTestGwt::handleMessageFromEmbeddedView(Ljava/lang/String;Lfr/openparts/OpenParts/client/OPParamsJS;)(message, paramsjs));
        }
    }-*/;

    /**
     * Register a dummy message from container slot handler callback.
     * 
     * @param dispatcher
     *            the dispatcher on which to register the handler callback.
     * @param tester
     *            this test class.
     */
    private final native void registerDummyContainerSlotHandlerCallback(final OPMessagesDispatcher dispatcher, final OPMessagesDispatcherTestGwt tester) /*-{
        dispatcher.messageFromContainerSlotHandlerCallback = function(message, paramsjs) {
            return $entry(tester.@fr.openparts.OpenParts.test.gwt.tests.OPMessagesDispatcherTestGwt::handleMessageFromContainerSlot(Ljava/lang/String;Lfr/openparts/OpenParts/client/OPParamsJS;)(message, paramsjs));
        }
    }-*/;
    
    /**
     * Register a dummy embedded view attach callback.
     * 
     * @param dispatcher
     *            the dispatcher on which to register the handler callback.
     * @param tester
     *            this test class.
     */
    private final native void registerDummyOnEmbeddedViewAttachCallback(final OPMessagesDispatcher dispatcher, final OPMessagesDispatcherTestGwt tester) /*-{
        dispatcher.onEmbeddedViewAttachCallback = function() {
            return $entry(tester.@fr.openparts.OpenParts.test.gwt.tests.OPMessagesDispatcherTestGwt::onEmbeddedViewAttach()());
        }
    }-*/;

    /**
     * Dummy message handler
     */
    public void handleMessageFromEmbeddedView(final String message, final OPParamsJS params) {
        ++embeddedViewHandlerCounter;
    }

    /**
     * Dummy message handler
     */
    public void handleMessageFromContainerSlot(final String message, final OPParamsJS params) {
        ++containerSlotHandlerCounter;
    }

    /**
     * Dummy embedded view attach handler 
     */
    public void onEmbeddedViewAttach() {
       ++embeddedViewAttachCounter;
    }
    
    /**
     * Return the dispatcher attached to the given element.
     * 
     * @param element
     *            the element on which to search for a dispatcher
     * @return the attached dispatcher, if any, or null if there is no dispatcher attached to this element.
     */
    private final native static OPMessagesDispatcher getDispatcherAttachedToElementl(final Element element) /*-{
        return element.messageDispatcher;
    }-*/;

    /**
     * Check if assertions are enabled.
     * 
     * @return true if assertions are enabled, false otherwise
     */
    private final boolean assertionsAreEnabled() {
        try {
            assert false;
            return false;
        } catch (AssertionError e) {
            return true;
        }
    }

}
